/**
 *
 */
package com.naiterui.ehp.bp.support.security;

import com.naiterui.ehp.bp.support.context.ResourceHolder;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.naiterui.ehp.bp.support.utils.SysConfigUtil;

/**
 * 登录拦截器
 * @author guoyongxiang
 * @since 1.0.0
 */
@Slf4j
public abstract class AbstractLoginInterceptor implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLoginInterceptor.class);

    private static PathMatcher pathMatcher = new AntPathMatcher();

    private static List<String> NOSIGN_URI = new ArrayList<>();

    private static List<String> NO_PRINT_LOG = new ArrayList<>();

    private static List<String> ONLY_INNER_ORIGIN = new ArrayList<>();

    public AbstractLoginInterceptor() {

        // 默认免校验地址
        NOSIGN_URI.add("/");
        NOSIGN_URI.add("/**/swagger/");
        NOSIGN_URI.add("/**/actuator/info");
        NOSIGN_URI.add("/**/actuator/health");
        NOSIGN_URI.add("/**/inner/supervise/**");
        NOSIGN_URI.add("/**/inner/**/supervise/**");
        NO_PRINT_LOG.add("/");
        NO_PRINT_LOG.add("/**/actuator/info");
        NO_PRINT_LOG.add("/**/actuator/health");
        NO_PRINT_LOG.add("/**/openapi/");

        // 设置仅内部来源列表
        //ONLY_INNER_ORIGIN.add(HeaderConstant.HEADER_ORIGIN_PATIENT_WX + "");
        ONLY_INNER_ORIGIN.add(HeaderConstant.HEADER_ORIGIN_DEPUTY_WX + "");
        ONLY_INNER_ORIGIN.add(HeaderConstant.HEADER_ORIGIN_HCP_PT_WX_MP + "");

        // 加载免登录列表
        this.settingNosignUri(NOSIGN_URI);

        // printNosignUri();
    }

    private static boolean checkSignIgnoreUrl(String reqUrl) {
        return NOSIGN_URI.stream().anyMatch(uri -> matchUrl(uri, reqUrl));
    }

    private static boolean matchUrl(String matchUrl, String reqUrl) {
        return pathMatcher.match(matchUrl, reqUrl);
    }

    private static boolean openLogger(String origin) {
        return !(HeaderConstant.HEADER_ORIGIN_MONITOR_SYSTEM + "").equals(origin);
    }

    private static void traceLogger(String origin, String msg, Object... args) {
        if (openLogger(origin)) {
            LOGGER.trace(msg, args);
        }
    }

    private static boolean noPrintLog(String reqUrl) {
        return NO_PRINT_LOG.stream().anyMatch(uri -> matchUrl(uri, reqUrl));
    }

    @Override
    public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (request.getMethod().equals("OPTIONS")) {
            return true;
        }

        // ************************************Header**************************************
        // 来源
        String origin = request.getHeader(HeaderConstant.HEADER_ORIGIN);

        // 内网请求标识(1内部)
        String inner = request.getHeader(SettingConfig.HEADER_INNER);
        // 即使传递X-inner标识，存在业务来源信息，依然不可忽略鉴权
        if (StringUtils.isNotBlank(origin)) {
            inner = null;
        }
        if (SysConfigUtil.getDebugMode() && ONLY_INNER_ORIGIN.contains(origin)) {
            inner = HeaderConstant.HEADER_INNER_REQUEST_YES + "";
        }

        // URL
        String url = request.getServletPath();
        String token = request.getParameter(SettingConfig.TOKEN);

        //若微信来源，单header中无token或token不合法(patientId为空)，则视为未登录,直接返回
        //String patientId = request.getParameter(SettingConfig.PATIENT_ID);
        //if ((HeaderConstant.HEADER_ORIGIN_PATIENT_WX + "").equals(origin) && StringUtils.isAllEmpty(token, patientId)) {
        //    return false;
        //}

        // 打印请求日志
        if (!noPrintLog(url)) {
            this.printAccessLog(request);
        }

        // 校验是否直接放行
        boolean accessPass = this.verfiyAccess(origin, inner, token, url);

        if (!accessPass) {
            accessPass = this.verifyToken(request, response);
        }

        return accessPass;

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("资源持有器线程上下文-请求结束清理数据-postHandle，数据信息 {}", ResourceHolder.getResourceMap());
        ResourceHolder.clear();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        log.info("资源持有器线程上下文-请求结束清理数据-afterCompletion，数据信息 {}", ResourceHolder.getResourceMap());
        ResourceHolder.clear();
    }

    protected abstract void printAccessLog(HttpServletRequest request);

    @SuppressWarnings("WeakerAccess")
    protected boolean verifyToken(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return false;
    }

    protected abstract void settingNosignUri(List<String> nosignUri);

    private boolean verfiyAccess(String origin, String inner, String token, String url) {

        // 微信来源(只走内网的来源)
        if (ONLY_INNER_ORIGIN.contains(origin)) {
            return this.verifyOnlyInnerAccess(origin, inner, url);
        }
        return this.verifyAppAndInnerAccess(origin, inner, token, url);
    }

    private boolean verifyOnlyInnerAccess(String origin, String inner, String url) {

        // 微信内部访问请求
        if ((HeaderConstant.HEADER_INNER_REQUEST_YES + "").equals(inner)) {
            return true;
        }
        traceLogger(origin, "[ACCESS-WX] outside request, err header. url:{}", url);
        // 特殊允许非内部来源请求接口，且免token检查
        return checkSignIgnoreUrl(url);
    }

    private boolean verifyAppAndInnerAccess(String origin, String inner, String token, String url) {
        // 服务间内部请求(不校验token)
        if ((HeaderConstant.HEADER_INNER_REQUEST_YES + "").equals(inner)) {

            // 不携带TOKEN
            if (StringUtils.isBlank(token)) {
                traceLogger(origin, "[ACCESS-INNER] inner request normal, url:{}", url);
            } else {
                // 内网接口不应携带token（先放行）
                traceLogger(origin, "[ACCESS-INNER] inner request should remove token. url:{}", url);
            }

        } else {
            // APP请求,非免登录接口校验TOKEN
            return checkSignIgnoreUrl(url);
        }
        return true;
    }

}
